package be.android.forap.sync;

import java.io.IOException;
import java.security.NoSuchProviderException;
import java.util.ArrayList;
import java.util.List;

import be.android.forap.dataprovider.ContactManager;
import be.android.forap.dataprovider.RawContact;
import be.android.forap.network.DropboxUtil;

import org.apache.http.ParseException;
import org.bouncycastle.openpgp.PGPException;
import org.json.JSONException;
import com.dropbox.client2.exception.DropboxException;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Service;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;

public class ContactsSyncAdapterService extends Service {

    private static final Object sSyncAdapterLock = new Object();
	private static SyncAdapter sSyncAdapter = null;

	@Override
	public void onCreate() {
		synchronized (sSyncAdapterLock) {
            if (sSyncAdapter == null) {
                sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
            }
        }
	}

	@Override
	public IBinder onBind(Intent intent) {
		return sSyncAdapter.getSyncAdapterBinder();
	}	

	private static class SyncAdapter extends AbstractThreadedSyncAdapter {
		private static final String TAG = "SyncAdapter";
		private static final String SYNC_MARKER_KEY = "be.android.forap.marker";

		private final AccountManager mAccountManager;
		private final Context mContext;

		//private static final boolean NOTIFY_AUTH_FAILURE = true;


		public SyncAdapter(Context context, boolean autoInitialize) {
			super(context, autoInitialize);
			mContext = context;
			mAccountManager = AccountManager.get(context);
		}

		@Override
		public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
			Log.i(TAG,"--- Starting sync ---");
			try {
				// see if we already have a sync-state attached to this account. By handing
				// This value to the server, we can just get the contacts that have
				// been updated on the server-side since our last sync-up
				long lastSyncMarker = getServerSyncMarker(account);

				// By default, contacts from a 3rd party provider are hidden in the contacts
				// list. So let's set the flag that causes them to be visible, so that users
				// can actually see these contacts.
				if (lastSyncMarker == 0) {
					ContactManager.setAccountContactsVisibility(getContext(), account, true);
				}

				List<RawContact> dirtyLocalContacts;
				List<RawContact> updatedContacts;

				// Use the account manager to request the AuthToken we'll need
				// to talk to our sample server.  If we don't have an AuthToken
				// yet, this could involve a round-trip to the server to request
				// and AuthToken.
				// final String authtoken = mAccountManager.blockingGetAuthToken(account, Constants.AUTHTOKEN_TYPE, NOTIFY_AUTH_FAILURE);

				// Make sure that the sample group exists
				//final long groupId = ContactManager.ensureForApGroupExists(mContext, account);

				long newSyncMarker = DropboxUtil.getNewServerSyncState();

				Log.i(TAG,"Sync process L:" + lastSyncMarker + " S:" + newSyncMarker );

				if(lastSyncMarker != newSyncMarker){
					if(lastSyncMarker>newSyncMarker){
						Log.i(TAG,"Deleting server contacts; putting all contacts of phone online");
						if(newSyncMarker != 0)
							DropboxUtil.deleteContactsFile();
						ContactManager.makeAllContactsDirty(mContext, account);
						List<RawContact> allLocalContacts = ContactManager.getDirtyContacts(mContext, account);


						if(!allLocalContacts.isEmpty()){
							// Send the dirty contacts to the server
							DropboxUtil.syncContacts(allLocalContacts);
							long newSyncState = DropboxUtil.getLocalSyncState();
							// Update the local contacts database with the changes. updateContacts()
							// returns a syncState value that indicates the high-water-mark for
							// the changes we received.
							Log.d(TAG, "Calling contactManager's sync contacts");
							//long newSyncState = ContactManager.updateContacts(mContext, account.name, updatedContacts, groupId, lastSyncMarker);

							// Save off the new sync marker. On our next sync, we only want to receive
							// contacts that have changed since this sync...
							setServerSyncMarker(account, newSyncState);

							ContactManager.clearSyncFlags(mContext, allLocalContacts);
						}
						//sync
					}else{
						Log.i(TAG,"Deleting some local contacts; putting all contacts of server local");
						// Someone else changed server contacts file.
						// Download file and make import this as last version
						List<RawContact> freshServerContacts = DropboxUtil.getServerContacts();
						List<RawContact> localContacts = ContactManager.getAllContacts(mContext, account.name);
						List<RawContact> deletableLocalContacts = new ArrayList<RawContact>();
						for(RawContact lc : localContacts){
							if(!freshServerContacts.contains(lc)){
								deletableLocalContacts.add(lc);
							}
						}
						ContactManager.deleteLocalContacts(deletableLocalContacts, mContext);
						ContactManager.updateContacts(mContext, account.name, freshServerContacts);
						//ContactManager.newContactList(mContext, account.name, freshServerContacts, groupId);
						setServerSyncMarker(account, DropboxUtil.getLocalSyncState());
					}
				} else {
					Log.i(TAG,"Sending latest changed to server");
					// Find the local 'dirty' contacts that we need to tell the server about...
					// Find the local users that need to be sync'd to the server...
					dirtyLocalContacts = ContactManager.getDirtyContacts(mContext, account);

					if(!dirtyLocalContacts.isEmpty()){
						// Send the dirty contacts to the server, and retrieve the server-side changes
						DropboxUtil.syncContacts(dirtyLocalContacts);
						long newSyncState = DropboxUtil.getLocalSyncState();
						// Update the local contacts database with the changes. updateContacts()
						// returns a syncState value that indicates the high-water-mark for
						// the changes we received.
						Log.d(TAG, "Calling contactManager's sync contacts");
						//long newSyncState = ContactManager.updateContacts(mContext, account.name, updatedContacts, groupId, lastSyncMarker);

						// Save off the new sync marker. On our next sync, we only want to receive
						// contacts that have changed since this sync...
						setServerSyncMarker(account, newSyncState);

						ContactManager.clearSyncFlags(mContext, dirtyLocalContacts);
					}
				}
			} catch (final ParseException e) {
				Log.e(TAG, "ParseException", e);
				syncResult.stats.numParseExceptions++;
			} catch (final JSONException e) {
				Log.e(TAG, "JSONException", e);
				syncResult.stats.numParseExceptions++;
			} catch (DropboxException e) {
				Log.e(TAG, "DropboxException", e);
				syncResult.stats.numParseExceptions++;
			} catch (NoSuchProviderException e) {
				Log.e(TAG, "NoSuchProviderException", e);
				syncResult.stats.numParseExceptions++;
			} catch (IOException e) {
				Log.e(TAG, "IOException", e);
				syncResult.stats.numParseExceptions++;
			} catch (PGPException e) {
				Log.e(TAG, "PGPException", e);
				syncResult.stats.numParseExceptions++;
			}
			Log.i(TAG,"--- Ending sync ---");
		}

		/**
		 * This helper function fetches the last known high-water-mark
		 * we received from the server - or 0 if we've never synced.
		 * @param account the account we're syncing
		 * @return the change high-water-mark
		 */
		private long getServerSyncMarker(Account account) {
			String markerString = mAccountManager.getUserData(account, SYNC_MARKER_KEY);
			if (!TextUtils.isEmpty(markerString)) {
				return Long.parseLong(markerString);
			}
			return 0;
		}

		/**
		 * Save off the high-water-mark we receive back from the server.
		 * @param account The account we're syncing
		 * @param marker The high-water-mark we want to save.
		 */
		private void setServerSyncMarker(Account account, long marker) {
			mAccountManager.setUserData(account, SYNC_MARKER_KEY, Long.toString(marker));
		}
	}
}	


/*
	 private static ContentResolver mContentResolver = null;

	private static String UsernameColumn = ContactsContract.RawContacts.SYNC1;
	private static String PhotoTimestampColumn = ContactsContract.RawContacts.SYNC2;
 * 
 * private SyncAdapter getSyncAdapter() {

		if (sSyncAdapter == null)
			sSyncAdapter = new SyncAdapter(this);
		return sSyncAdapter;
	}

	private static void addContact(Account account, String name, String username) {
		Log.i(TAG, "Adding contact: " + name);
		ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();


		/*ContentResolver.setSyncAutomatically(account,ContactsContract.AUTHORITY, true);
		ContentProviderClient client = getContentResolver().acquireContentProviderClient(ContactsContract.AUTHORITY_URI);
		ContentValues cv = new ContentValues();
		cv.put(Groups.ACCOUNT_NAME, account.name);
		cv.put(Groups.ACCOUNT_TYPE, account.type);
		cv.put(Settings.UNGROUPED_VISIBLE, true);
		try {
		client.insert(Settings.CONTENT_URI.buildUpon()      .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
		.build(), cv);
		} catch (RemoteException e) {...}*/

/*ContentResolver cr = getContentResolver();
		ContentValues values = new ContentValues();
		values.put(ContactsContract.Settings.ACCOUNT_TYPE, syncAccountType);
		values.put(ContactsContract.Settings.ACCOUNT_NAME, syncAccountName);
		values.put(ContactsContract.Settings.UNGROUPED_VISIBLE, 1);
		values.put(ContactsContract.Settings.SHOULD_SYNC, 0);
		cr.insert(android.provider.ContactsContract.Settings.CONTENT_URI, values);*/


/*ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
		builder.withValue(RawContacts.ACCOUNT_NAME, account.name);
		builder.withValue(RawContacts.ACCOUNT_TYPE, account.type);
		//builder.withValue(RawContacts.Settings.UNGROUPED_VISIBLE, "true");
		builder.withValue(RawContacts.SYNC1, username);
		operationList.add(builder.build());

		builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
		builder.withValueBackReference(ContactsContract.CommonDataKinds.StructuredName.RAW_CONTACT_ID, 0);
		builder.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
		builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
		operationList.add(builder.build());

		builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
		builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0);
		builder.withValue(ContactsContract.Data.MIMETYPE, "vnd.android.cursor.item/vnd.be.android.forap.profile");
		builder.withValue(ContactsContract.Data.DATA1, username);
		builder.withValue(ContactsContract.Data.DATA2, "SyncProviderDemo Profile");
		builder.withValue(ContactsContract.Data.DATA3, "View profile");
		operationList.add(builder.build());

		try {
			mContentResolver.applyBatch(ContactsContract.AUTHORITY, operationList);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static void updateContactStatus(ArrayList<ContentProviderOperation> operationList, long rawContactId, String status) {
		Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
		Uri entityUri = Uri.withAppendedPath(rawContactUri, Entity.CONTENT_DIRECTORY);
		Cursor c = mContentResolver.query(entityUri, new String[] { RawContacts.SOURCE_ID, Entity.DATA_ID, Entity.MIMETYPE, Entity.DATA1 }, null, null, null);
		try {
			while (c.moveToNext()) {
				if (!c.isNull(1)) {
					String mimeType = c.getString(2);

					if (mimeType.equals("vnd.android.cursor.item/vnd.org.c99.SyncProviderDemo.profile")) {
						ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(ContactsContract.StatusUpdates.CONTENT_URI);
						builder.withValue(ContactsContract.StatusUpdates.DATA_ID, c.getLong(1));
						builder.withValue(ContactsContract.StatusUpdates.STATUS, status);
						builder.withValue(ContactsContract.StatusUpdates.STATUS_RES_PACKAGE, "be.android.forap");
						builder.withValue(ContactsContract.StatusUpdates.STATUS_LABEL, R.string.app_name);
						builder.withValue(ContactsContract.StatusUpdates.STATUS_ICON, R.drawable.signature);
						builder.withValue(ContactsContract.StatusUpdates.STATUS_TIMESTAMP, System.currentTimeMillis());
						operationList.add(builder.build());

						//Only change the text of our custom entry to the status message pre-Honeycomb, as the newer contacts app shows
						//statuses elsewhere
						if(Integer.decode(Build.VERSION.SDK) < 11) {
							builder = ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI);
							builder.withSelection(BaseColumns._ID + " = '" + c.getLong(1) + "'", null);
							builder.withValue(ContactsContract.Data.DATA3, status);
							operationList.add(builder.build());
						}
					}
				}
			}
		} finally {
			c.close();
		}
	}

	private static void updateContactPhoto(ArrayList<ContentProviderOperation> operationList, long rawContactId, byte[] photo) {
		ContentProviderOperation.Builder builder = ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI);
		builder.withSelection(ContactsContract.Data.RAW_CONTACT_ID + " = '" + rawContactId
				+ "' AND " + ContactsContract.Data.MIMETYPE + " = '" + ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE + "'", null);
		operationList.add(builder.build());

		try {
			if(photo != null) {
				builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
				builder.withValue(ContactsContract.CommonDataKinds.Photo.RAW_CONTACT_ID, rawContactId);
				builder.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE);
				builder.withValue(ContactsContract.CommonDataKinds.Photo.PHOTO, photo);
				operationList.add(builder.build());

				builder = ContentProviderOperation.newUpdate(ContactsContract.RawContacts.CONTENT_URI);
				builder.withSelection(ContactsContract.RawContacts.CONTACT_ID + " = '" + rawContactId + "'", null);
				builder.withValue(PhotoTimestampColumn, String.valueOf(System.currentTimeMillis()));
				operationList.add(builder.build());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static class SyncEntry {
		public Long raw_id = 0L;
		public Long photo_timestamp = null;
	}

	private static void performSync(Context context, Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult)
			throws OperationCanceledException {
		HashMap<String, SyncEntry> localContacts = new HashMap<String, SyncEntry>();
		mContentResolver = context.getContentResolver();
		Log.i(TAG, "performSync: " + account.toString());

		// Load the local contacts
		Uri rawContactUri = RawContacts.CONTENT_URI.buildUpon().appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name).appendQueryParameter(
				RawContacts.ACCOUNT_TYPE, account.type).build();
		Cursor c1 = mContentResolver.query(rawContactUri, new String[] { BaseColumns._ID, UsernameColumn, PhotoTimestampColumn }, null, null, null);
		while (c1.moveToNext()) {
			Log.i(TAG, "contact");
			SyncEntry entry = new SyncEntry();
			entry.raw_id = c1.getLong(c1.getColumnIndex(BaseColumns._ID));
			entry.photo_timestamp = c1.getLong(c1.getColumnIndex(PhotoTimestampColumn));
			localContacts.put(c1.getString(1), entry);
		}

		ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
		try {
			// If we don't have any contacts, create one. Otherwise, set a
			// status message
			if (localContacts.get("efudd") == null) {
				addContact(account, "Elmer Fudd", "efudd");
			} else {
				if (localContacts.get("efudd").photo_timestamp == null || System.currentTimeMillis() > (localContacts.get("efudd").photo_timestamp + 604800000L)) {
					//You would probably download an image file and just pass the bytes, but this sample doesn't use network so we'll decode and re-compress the icon resource to get the bytes
					ByteArrayOutputStream stream = new ByteArrayOutputStream();
					Bitmap icon = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);
					icon.compress(CompressFormat.PNG, 0, stream);
					updateContactPhoto(operationList, localContacts.get("efudd").raw_id, stream.toByteArray());
				}
				updateContactStatus(operationList, localContacts.get("efudd").raw_id, "hunting wabbits");
			}
			if (operationList.size() > 0)
				mContentResolver.applyBatch(ContactsContract.AUTHORITY, operationList);
		} catch (Exception e1) {
			e1.printStackTrace();
		}
	}*/
